
package com.guomei.service.utilService;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.*;
import com.guomei.bean.curse.CourseVideo;
import com.guomei.configs.AliyunOssConfig;
import com.guomei.service.curse.CourseVideoService;
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.FFmpegFrameRecorder;
import org.bytedeco.javacv.Frame;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.*;
import java.util.*;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import static sun.font.CreatedFontTracker.MAX_FILE_SIZE;

@Service
@Slf4j
public class VideoTranscodeService {
    @Autowired
    private OSS ossClient;
    @Autowired
    private AliyunOssConfig aliyunOssConfig;
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    @Value("${video.temp-dir}")
    private String tempDir;
    @Resource
    private CourseVideoService courseVideoService;
    @Value("${video.segment-duration:10}")
    private int segmentDuration;

    private boolean ffmpegAvailable = false;
    // 分片大小：5MB（需符合阿里云限制：单分片100KB~5GB，总分片1~10000个）
    private static final long PART_SIZE = 5 * 1024 * 1024;
    // 最大文件限制：2GB（根据需求调整）
    private static final long MAX_FILE_SIZE = 2L * 1024 * 1024 * 1024;
    private static final String UPLOAD_PREFIX = "video:upload:";
    private static final long REDIS_EXPIRE_TIME = 30; // 30分钟
    @PostConstruct
    public void init() {
        try {
            // 使用JavaCPP的加载机制
            org.bytedeco.javacpp.Loader.load(org.bytedeco.ffmpeg.global.avutil.class);
            ffmpegAvailable = true;
            log.info("FFmpeg library loaded successfully");
        } catch (Exception e) {
            log.warn("FFmpeg library not available: {}", e.getMessage());
            ffmpegAvailable = false;
        }
    }

    /**
     * 处理视频：转码为多分辨率HLS格式并上传至OSS
     */
    public Map<String, String> processVideo(MultipartFile file) throws Exception {
        if (!ffmpegAvailable) {
            throw new RuntimeException("视频处理功能不可用，请联系管理员");
        }

        // 生成唯一视频ID
        String videoId = UUID.randomUUID().toString();
        // 创建本地临时目录（自动创建不存在的目录）
        String localVideoDir = tempDir + File.separator + videoId;
        File localDir = new File(localVideoDir);

        // 关键：自动创建多级目录，解决目录不存在问题
        if (!localDir.exists() && !localDir.mkdirs()) {
            throw new RuntimeException("无法创建临时目录: " + localVideoDir);
        }

        try {
            // 1. 保存上传文件到临时目录
            String originalFileName = file.getOriginalFilename();
            String localFilePath = localVideoDir + File.separator + originalFileName;
            try (FileOutputStream fos = new FileOutputStream(localFilePath)) {
                fos.write(file.getBytes());
            }

            // 2. 定义两种分辨率参数 (宽度, 高度, 目录名)
            Object[][] resolutions = {
                    {1920, 1080, "1080p"},
                    {2560, 1440, "2k"}
            };

            // 存储各分辨率的OSS播放地址
            Map<String, String> result = new HashMap<>();

            // 3. 为每种分辨率转码并上传
            for (Object[] res : resolutions) {
                int width = (int) res[0];
                int height = (int) res[1];
                String resDirName = (String) res[2];

                try {
                    // 转码为HLS格式
                    String m3u8LocalPath = transcodeToHls(localFilePath, localVideoDir, resDirName, width, height);

                    // 上传到OSS
                    String ossM3u8Url = uploadHlsToOss(localVideoDir, resDirName, videoId, m3u8LocalPath);
                    result.put(resDirName, ossM3u8Url);
                } catch (Exception e) {
                    log.error("处理分辨率 {} 失败", resDirName, e);
                    // 继续处理其他分辨率
                }
            }

            return result;
        } finally {
            // 清理本地临时文件
            deleteDirectory(localDir);
            log.info("本地临时文件已清理: {}", localVideoDir);
        }
    }

    /**
     * 使用JavaCV转码视频为HLS格式
     */
    private String transcodeToHls(String sourcePath, String parentDir, String resDirName, int width, int height) throws Exception {
        // 创建分辨率专属目录
        String resDir = parentDir + File.separator + resDirName;
        File resDirFile = new File(resDir);
        if (!resDirFile.exists() && !resDirFile.mkdirs()) {
            throw new RuntimeException("无法创建分辨率目录: " + resDir);
        }

        // 输出的m3u8文件路径
        String m3u8FileName = resDirName + ".m3u8";
        String outputM3u8Path = resDir + File.separator + m3u8FileName;

        // 初始化抓取器
        try (FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(sourcePath)) {
            grabber.start();

            // 初始化录制器
            try (FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(outputM3u8Path, width, height)) {
                // 配置HLS参数
                recorder.setFormat("hls");
                recorder.setOption("hls_time", String.valueOf(segmentDuration));  // 分片时长
                recorder.setOption("hls_list_size", "0");                         // 保留所有分片
                recorder.setOption("hls_segment_filename", resDir + File.separator + "%03d.ts");  // 分片命名

                // 视频编码配置
                recorder.setFrameRate(grabber.getFrameRate());
                recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);
                recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);

                // 音频编码配置 - 关键修复部分
                if (grabber.getAudioChannels() > 0) {
                    recorder.setAudioChannels(grabber.getAudioChannels());
                    recorder.setSampleRate(grabber.getSampleRate());
                    recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
                }

                recorder.start();

                // 逐帧处理
                Frame frame;
                while ((frame = grabber.grabFrame()) != null) {
                    recorder.record(frame);
                }

                log.info("转码完成: {} -> {}", resDirName, outputM3u8Path);
                return outputM3u8Path;
            }
        } catch (Exception e) {
            log.error("转码失败: {} -> {}", resDirName, outputM3u8Path, e);
            throw e;
        }
    }

    /**
     * 上传HLS文件到OSS
     */
    private String uploadHlsToOss(String localVideoDir, String resDirName, String videoId, String m3u8LocalPath) {
        String ossDir = aliyunOssConfig.getDir().getPrefix() + videoId + "/" + resDirName + "/";

        // 上传m3u8文件和所有ts分片
        File resDir = new File(localVideoDir + File.separator + resDirName);
        File[] files = resDir.listFiles();

        if (files != null) {
            for (File file : files) {
                try {
                    String ossKey = ossDir + file.getName();
                    ossClient.putObject(new PutObjectRequest(aliyunOssConfig.getBucketName(), ossKey, file));
                    log.info("上传OSS成功: {} -> {}", file.getName(), ossKey);
                } catch (Exception e) {
                    log.error("上传文件失败: {}", file.getName(), e);
                }
            }
        }

        // 返回m3u8的OSS访问URL
        return aliyunOssConfig.getDomain() + "/" + ossDir + resDirName + ".m3u8";
    }

    /**
     * 递归删除目录
     */
    private void deleteDirectory(File dir) {
        if (dir.isDirectory()) {
            File[] files = dir.listFiles();
            if (files != null) {
                for (File file : files) {
                    deleteDirectory(file);
                }
            }
        }
        if (!dir.delete()) {
            log.warn("无法删除文件或目录: {}", dir.getAbsolutePath());
        }
    }


    /**
     * 处理上传请求，立即返回状态，实际上传在异步线程中执行
     */
    public Map<String, Object> handleUploadRequest(MultipartFile file, String ossObjectKey, CourseVideo courseVideo) {
        // 前置校验
        Map<String, Object> result = new HashMap<>();
        if (file.isEmpty()) {
            result.put("msg", "文件不能为空");
            result.put("code", 200);
            return result;
        }

        long fileSize = file.getSize();
        if (fileSize > MAX_FILE_SIZE) {
            result.put("msg", "文件大小超过限制");
            result.put("code", 200);
            return result;
        }

        // 生成上传ID作为标识
        String uploadId = UUID.randomUUID().toString();
        // 生成Redis的key
        String redisKey = UPLOAD_PREFIX + uploadId;

        // 先在Redis中设置"上传中"状态
        redisTemplate.opsForValue().set(redisKey, "上传中...", REDIS_EXPIRE_TIME, TimeUnit.MINUTES);

        // 启动异步上传
        try {
            asyncMultipartUploadWithBreakPoint(file, ossObjectKey, courseVideo, uploadId, redisKey);
            result.put("msg", "正在上传中，请稍后查询结果");
            result.put("code", 200);
            result.put("data", uploadId);

            return result;
        } catch (Exception e) {
            redisTemplate.opsForValue().set(redisKey, "上传失败: " + e.getMessage(),
                    REDIS_EXPIRE_TIME, TimeUnit.MINUTES);
            result.put("code", 200);
            result.put("msg", "上传失败：" + e.getMessage());
            return result;
        }
    }

    /**
     * 异步执行分片上传+断点续传
     */
    @Async
    public Future<String> asyncMultipartUploadWithBreakPoint(MultipartFile file, String ossObjectKey,
                                                             CourseVideo courseVideo, String uploadId,
                                                             String redisKey) throws Exception {
        String result = null;
        try {
            log.info("开始异步上传文件: {}, uploadId: {}, 章节ID: {}",
                    ossObjectKey, uploadId, courseVideo.getChapterId());

            // 1. 初始化分片上传
            InitiateMultipartUploadRequest initRequest = new InitiateMultipartUploadRequest(
                    aliyunOssConfig.getBucketName(), ossObjectKey);
            InitiateMultipartUploadResult initResult = ossClient.initiateMultipartUpload(initRequest);
            String actualUploadId = initResult.getUploadId();
            log.info("初始化分片上传成功, actualUploadId: {}", actualUploadId);

            // 2. 计算分片数量
            long fileSize = file.getSize();
            int partCount = (int) (fileSize % PART_SIZE == 0 ? fileSize / PART_SIZE : fileSize / PART_SIZE + 1);
            log.info("计算分片信息: 文件大小={}, 分片大小={}, 分片数量={}", fileSize, PART_SIZE, partCount);

            // 3. 查询已上传的分片
            List<PartSummary> uploadedParts = listUploadedParts(actualUploadId, ossObjectKey);
            List<Integer> uploadedPartNumbers = new ArrayList<>();
            for (PartSummary part : uploadedParts) {
                uploadedPartNumbers.add(part.getPartNumber());
            }
            log.info("已上传分片数量: {}, 分片号列表: {}", uploadedPartNumbers.size(), uploadedPartNumbers);

            // 4. 上传未完成的分片
            List<PartETag> partETags = new ArrayList<>();
            int uploadedCount = 0;

            for (int partNumber = 1; partNumber <= partCount; partNumber++) {
                // 更新Redis中的上传进度
                int progress = (int) (((float) partNumber / partCount) * 100);
                redisTemplate.opsForValue().set(redisKey, "上传中...(" + progress + "%)",
                        REDIS_EXPIRE_TIME, TimeUnit.MINUTES);

                if (uploadedPartNumbers.contains(partNumber)) {
                    log.debug("跳过已上传分片: {}", partNumber);
                    continue;
                }

                // 读取分片字节
                byte[] partBytes = readPartBytes(file, partNumber, fileSize);
                log.debug("读取分片数据: 分片号={}, 大小={} bytes", partNumber, partBytes.length);

                // 上传分片
                try (InputStream in = new ByteArrayInputStream(partBytes)) {
                    UploadPartRequest uploadRequest = new UploadPartRequest();
                    uploadRequest.setBucketName(aliyunOssConfig.getBucketName());
                    uploadRequest.setKey(ossObjectKey);
                    uploadRequest.setUploadId(actualUploadId);
                    uploadRequest.setPartNumber(partNumber);
                    uploadRequest.setInputStream(in);
                    uploadRequest.setPartSize(partBytes.length);

                    UploadPartResult uploadResult = ossClient.uploadPart(uploadRequest);
                    partETags.add(uploadResult.getPartETag());
                    uploadedCount++;
                    log.debug("上传分片成功: 分片号={}, ETag={}", partNumber, uploadResult.getETag());
                }
            }
            log.info("分片上传完成: 总计={}, 已上传={}, 新上传={}", partCount, uploadedPartNumbers.size(), uploadedCount);

            // 5. 合并分片
            log.info("开始合并分片, 分片数量: {}", partETags.size());
            CompleteMultipartUploadRequest completeRequest = new CompleteMultipartUploadRequest(
                    aliyunOssConfig.getBucketName(), ossObjectKey, actualUploadId, partETags);
            CompleteMultipartUploadResult completeResult = ossClient.completeMultipartUpload(completeRequest);
            log.info("合并分片完成, 文件访问地址: {}", completeResult.getLocation());

            result = completeResult.getLocation();
            courseVideo.setVideoUrl(result);
            courseVideo.setStatus(1);
            courseVideoService.save(courseVideo);
            // 上传成功，更新Redis中的地址
            redisTemplate.opsForValue().set(redisKey, result, REDIS_EXPIRE_TIME, TimeUnit.MINUTES);

            return new AsyncResult<>(result);

        } catch (Exception e) {
            log.error("上传过程中发生异常: uploadId={}, ossObjectKey={}", uploadId, ossObjectKey, e);
            String errorMsg = "上传失败: " + e.getMessage();
            redisTemplate.opsForValue().set(redisKey, errorMsg, REDIS_EXPIRE_TIME, TimeUnit.MINUTES);

            // 异常时取消上传
            try {
                ossClient.abortMultipartUpload(new AbortMultipartUploadRequest(
                        aliyunOssConfig.getBucketName(), ossObjectKey, uploadId));
                log.info("已取消上传任务: {}", uploadId);
            } catch (Exception abortEx) {
                log.warn("取消上传任务失败: {}", uploadId, abortEx);
            }

            return new AsyncResult<>(errorMsg);
        }
    }
    /**
     * 查询上传结果的接口
     */
    public String queryUploadResult(String upoloadId) {
        String redisKey = UPLOAD_PREFIX + upoloadId;
        return redisTemplate.opsForValue().get(redisKey);
    }

    /**
     * 查询已上传的分片列表（返回实际类型List<PartSummary>）
     */
    private List<PartSummary> listUploadedParts(String uploadId, String ossObjectKey) {
        ListPartsRequest listRequest = new ListPartsRequest(
                aliyunOssConfig.getBucketName(), ossObjectKey, uploadId);
        listRequest.setMaxParts(1000);  // 最大返回1000个分片

        List<PartSummary> allParts = new ArrayList<>();
        PartListing partListing;
        do {
            partListing = ossClient.listParts(listRequest);
            allParts.addAll(partListing.getParts());

            // 使用正确的分页方法
            listRequest.setPartNumberMarker(partListing.getNextPartNumberMarker());
        } while (partListing.isTruncated());

        return allParts;
    }

    /**
     * 读取MultipartFile中指定分片的字节
     */
    private byte[] readPartBytes(MultipartFile file, int partNumber, long fileSize) throws IOException {
        long startPos = (partNumber - 1) * PART_SIZE;
        long endPos = Math.min(startPos + PART_SIZE, fileSize);
        long partLength = endPos - startPos;

        byte[] partBytes = new byte[(int) partLength];
        try (InputStream in = file.getInputStream()) {
            in.skip(startPos);  // 跳过起始位置
            int read = in.read(partBytes);
            if (read != partLength) {
                log.info("分片读取不完整");
                throw new IOException("分片读取不完整，预期" + partLength + "字节，实际" + read + "字节");
            }
        }
        return partBytes;
    }
}